package servidor;

import java.io.*;
import java.net.*;
import java.net.DatagramSocket;
import java.util.ArrayList;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.Logger;

public class ServidorUDP {

    private DatagramSocket socketServidor;
    private ArrayList<DatagramPacket> requisicoes;
    private InetAddress ip;
    private InetAddress ipALL; // ip para enviar para todos do chat
    private InetAddress ipFUNC; // ip para enviar para todos os funcionarios
    private InetAddress ipCLI; // ip para enviar para todos os clientes
    private boolean encerrar; // sinal lógico para desligar servidor
    private ArrayList<User> usuarios; // Lista de todos os usuarios
    private ArrayList<User> usuarios_online;// Lista de todos os usuarios onlines
    private String login, senha, funcao; // Strings auxilizares, usadas para realizar logins
    private File logins; // arquivo com os logins de usuários
    private BufferedReader in;
    private ThreadAtualizaOnline thread_lista;
    private AccRequisicao thread_requisicao;
    private boolean atualizar = true;
    private Lock tranca;
    private Condition condicao;

    public ServidorUDP() throws IOException {
        requisicoes = new ArrayList<DatagramPacket>();
        ip = InetAddress.getByName("127.0.0.1");
        socketServidor = new DatagramSocket(9876);
        socketServidor.setReuseAddress(true); // seta para que o mesmo ip seja usado varias vezes (p/ testar cliente na mesma maquina)
        System.out.println("# Servidor rodando em : " + ip + ":" + 9876);
        usuarios_online = new ArrayList<User>();
        tranca = new ReentrantLock();
        condicao = tranca.newCondition();
        //inicia as threads
        //User novo = new User("novo", "1234", "user", InetAddress.getByName("localhost"), 12332);
        //User novo1 = new User("novo1", "1234", "user", InetAddress.getByName("localhost"), 12330);
        //User novo2 = new User("novo2", "1234", "user", InetAddress.getByName("localhost"), 12336);
       // User novo3 = new User("novo3", "1234", "user", InetAddress.getByName("localhost"), 12337);
        //User novo4 = new User("novo4", "1234", "user", InetAddress.getByName("localhost"), 12338);
       // usuarios_online.add(novo);
       // usuarios_online.add(novo1);
        //usuarios_online.add(novo2);
        //usuarios_online.add(novo3);
        //usuarios_online.add(novo4);
        thread_lista = new ThreadAtualizaOnline(usuarios_online, atualizar, this);
        thread_lista.start();
        thread_requisicao = new AccRequisicao(socketServidor, this, requisicoes);
        thread_requisicao.start();
        


        carregaLogins();//Carrega usuarios cadastrados na lista
    }

    // Ler Logins dos registros (.txt) e carrega na lista de usuarios
    private void carregaLogins() throws FileNotFoundException, IOException {
        in = new BufferedReader(new FileReader("Logs.txt"));
        String s;
        usuarios = new ArrayList<User>();
        System.out.println("\n>> Usuarios cadastrados :");
        while ((s = in.readLine()) != null) {
            login = s.split(":")[1];
            senha = s.split(":")[2];
            funcao = s.split(":")[3];
            //System.out.println(s); // [teste]
            System.out.println(">> " + login + "|" + senha + "|" + funcao + "|");// [teste]
            usuarios.add(new User(login, senha, funcao));
        }
    }

    public User consultarUsuario(String login, String senha) {
        for (int i = 0; i < usuarios.size(); i++) {
            if (login.equals(usuarios.get(i).getLogin()) && senha.equals(usuarios.get(i).getSenha())) {
                return usuarios.get(i);
            }
        }
        return null;
    }

    public User consultarUsuario(String login, ArrayList<User> lista) {
        for (int i = 0; i < lista.size(); i++) {
            if (login.equals(lista.get(i).getLogin())) {
                return lista.get(i);
            }
        }
        return null;
    }

    public synchronized void addRequisicao(DatagramPacket pacote) {
        requisicoes.add(pacote);
        notifyAll();
    }

    public synchronized DatagramPacket getRequisicao() {
        while (requisicoes.isEmpty()) {
            try {
                wait();
            } catch (InterruptedException ex) {
                Logger.getLogger(ServidorUDP.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
        notifyAll();
        return requisicoes.remove(0);
    }

   public void Tranca(User user){
       System.out.println("ESTADO DA THREAD ANTES DA TRANCA>>>"+thread_lista.getState());
       tranca.lock();
       try {
                        while (thread_lista.getState().compareTo(Thread.State.TIMED_WAITING) != 0) {
                            try {
                                condicao.await();
                            } catch (InterruptedException ex) {
                                Logger.getLogger(ServidorUDP.class.getName()).log(Level.SEVERE, null, ex);
                            }
                        }
                        usuarios_online.add(user);
                        condicao.signalAll();
                    } finally {
                        tranca.unlock();
                        System.out.println("ESTADO DA THREAD DEPOIS DA TRANCA>>>"+thread_lista.getState());
                    }
   }

    public void iniciar() throws IOException {
        User user; // usado para consultas de usuário
        //byte[] dadosRecebidos = new byte[1024];
        byte[] dadosEnviados = new byte[1024];

        System.out.println("-----------------------------------");
        while (!encerrar) {

            /*
             * Cria pacote para receber dados
             */
            String sTemp = null;
            /*
             * Recebe datagrama
             */
            System.out.println("pega req");
            DatagramPacket pacote = getRequisicao();
            String s = new String(pacote.getData());
            // obtem endereço e porta do transmissor da mensagem
            InetAddress ipTransmissor = pacote.getAddress();
            int porta = pacote.getPort();

            System.out.println(ipTransmissor + ":" + porta + " >> " + s.substring(0, s.lastIndexOf(":") + 1));

            // --------------------------------- Processo de Login ----------------------------------------------
            if (s.startsWith("LOG:")) {
                login = s.split(":")[1];
                senha = s.split(":")[2];

                System.out.println(">> Login >" + login + "< \n" + ">> Senha >" + senha + "<");

                /*      Verifica login                 */
                if ((user = consultarUsuario(login, senha)) != null) {

                    System.out.println(">> Login confirmado");
                    user.setOnline(true); // indica que o usuário está online
                    user.setIp(ipTransmissor);
                    user.setPorta(porta);
                    Tranca(user);
                    //usuarios_online.add(user);
                    // envia confirmação de cadastro
                    dadosEnviados = ":OK:".getBytes();
                    pacote = new DatagramPacket(dadosEnviados, dadosEnviados.length, user.getIp(), 12345);
                    System.out.println("TESTE >> " + new String(pacote.getData()));
                    socketServidor.send(pacote);

                    System.out.println(" -- " + ipTransmissor + ":" + 12345);
                } else {
                    dadosEnviados = ":NREG:".getBytes();
                    pacote = new DatagramPacket(dadosEnviados, dadosEnviados.length, ipTransmissor, 12345);
                    socketServidor.send(pacote);
                    System.out.println(" Não cadastrado");
                }
                System.out.println("-----------------------------------");
            }

            // --------------------------------- Processo de Envio de Mensagem ---------------------------------------
            if (s.startsWith("MSG:")) {

                //verifica se a mensagem é para algum grupo pré determinado, ou para todos
                // verifica se a mensagem é para todos
                if (s.split(":")[1].equals("ALL")) {
                } // verifica se a mensagem é para os funcionários
                else if (s.split(":")[1].equals("FUNC")) {
                } // verifica se a mensagem é para clientes
                else if (s.split(":")[1].equals("CLI")) {
                } // envia mensagem unicast, para um cliente determinado no 
                else {
                    if (!usuarios_online.isEmpty()) {
                        user = consultarUsuario(s.split(":")[1], usuarios_online);

                        if ((user!=null)&&(user.isOnline())) {
                            sTemp = "MSG:".concat(s.split(":")[2].concat(":"));
                            System.out.println("O Q TA RETORNANDO"+sTemp);
                            dadosEnviados = sTemp.getBytes();
                            pacote = new DatagramPacket(dadosEnviados, dadosEnviados.length, user.getIp(), 12345);
                        } else {
                            sTemp = "ALT:Falha na comunicação:";
                            dadosEnviados = sTemp.getBytes();
                            pacote = new DatagramPacket(dadosEnviados, dadosEnviados.length, ipTransmissor, 12345);
                        }
                        socketServidor.send(pacote);
                        System.out.println("IP DESTINO"+pacote.getAddress()+"PORTA DESTINO"+pacote.getPort());
                    }
                }
            }

            if (s.startsWith("CAD:")) {
                login = s.split(":")[1];
                senha = s.split(":")[2];
                User temp = consultarUsuario(login, usuarios);


                if (temp != null) {
                    sTemp = "ERR";
                    dadosEnviados = sTemp.getBytes();
                    pacote = new DatagramPacket(dadosEnviados, dadosEnviados.length, ipTransmissor, 12345);

                } else {

                    BufferedWriter salva_cad = new BufferedWriter(new FileWriter("Logs.txt", true));

                    salva_cad.append(":" + login + ":" + senha + ":user:");
                    salva_cad.newLine();
                    salva_cad.close();
                    User novo = new User(login, senha, "user");
                    novo.setIp(ipTransmissor);
                    novo.setOnline(true);
                    usuarios.add(novo);
                    System.out.println("IP Q MANDOU"+ipTransmissor);
                    Tranca(novo);
                    sTemp = ":GOO:";
                    dadosEnviados = sTemp.getBytes();
                    pacote = new DatagramPacket(dadosEnviados, dadosEnviados.length, novo.getIp(), 12345);


                }
                System.out.println(new String(dadosEnviados));
                //System.out.println(porta);

                socketServidor.send(pacote);
            }

        }

        // -------------------------------------------------------------------------------------------------------

        System.out.println("-----------------------------------");

        /*
         * caso o valor lógico "encerrar" seja verdadeiro o programa servidor
         * encerra, fechando as conexoes e streams
         */
        encerrar();
    }


    public void encerrar() throws IOException {
        socketServidor.close();
        in.close();
    }

    public void envioTeste() throws UnknownHostException, IOException {
        byte[] dadosEnviados = new byte[512];
        dadosEnviados = "MSG: Ola:".getBytes();
        DatagramPacket pacote = new DatagramPacket(dadosEnviados, dadosEnviados.length, InetAddress.getByName("127.0.0.1"), 12345);
        socketServidor.send(pacote);

        // essas mesagens são perdidas
        dadosEnviados = "MSG: testando envio:".getBytes();
        socketServidor.send(pacote);
        dadosEnviados = "MSG: OK:".getBytes();
        socketServidor.send(pacote);
    }

    public static void main(String args[]) throws SocketException, IOException {
        ServidorUDP server = new ServidorUDP();
        server.iniciar();
    }
}
